package ac.qnlm.util;/*
 * Utility.java
 * Copyright (C) 2020 yumaoxue <yumaoxue@qnlm.ac>
 *
 * Distributed under terms of the MIT license.
 */


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Utility {

    public Utility() {

        athreadDMA.add("\n//ATHREAD DMA transfer definition ");
        athreadDMA.add("\n__thread_local unsigned int D_COUNT = 0;");
        athreadDMA.add("\n__thread_local crts_rply_t dma_rply = 0;");
        athreadDMA.add("\n__thread_local crts_rply_t l_rply = 0;");
        athreadDMA.add("\n__thread_local crts_rply_t r_rply = 0;\n\n");


        globalDef.add("\n//ATHREAD global definition ");
        globalDef.add("\n__thread_local int warp_size = 32; ");
        globalDef.add("\n__thread_local int blockIndex;");
        globalDef.add("\n__thread_local Para* Para_s;\n\n");

        includedefinelist.add("\n//ATHREAD include file");
        includedefinelist.add("\n#include <crts.h>");
        includedefinelist.add("\n#include <slave.h>");
        includedefinelist.add("\n#include <simd.h>");
        includedefinelist.add("\n#include <math.h>");
        includedefinelist.add("\n#include <stdio.h>");
        includedefinelist.add("\n#include <float.h>");
        includedefinelist.add("\n#include <string.h>");
        includedefinelist.add("\n#include <stdbool.h>");
        includedefinelist.add("\n#include \"swperf.h\" \n\n");
       // includedefinelist.add("\n#define CP_CORE      64");
       // includedefinelist.add("\n#define RCV_CORE     0");
       // includedefinelist.add("\n#define DATAPAKAGE   16\n\n");

        dynamicDef.add("\n//dynamic atomic definition");

        atomicDef.add("\n__thread_local volatile int   rw_rply[CP_CORE]                   __attribute__ ((aligned(64))) = {0};");
        atomicDef.add("\n__thread_local volatile int   r_end[CP_CORE]                     __attribute__ ((aligned(64))) = {0};");
        atomicDef.add("\n__thread_local volatile int   arrayInd[RCV_CORE]                 __attribute__ ((aligned(64))) = {0};");
        atomicDef.add("\n__thread_local volatile int   *cpt_rply = 0;");
        atomicDef.add("\n__thread_local volatile int   compute_end = 0;");
        atomicDef.add("\n__thread_local int            dataStride = 0;");
        atomicDef.add("\n__thread_local int            dmaSize = 0;");
        atomicDef.add("\n__thread_local int            receiveId = 0;\n\n");

        //kernelPara.append("cuGrid[3],cuBlock[3]");
        kernelPara.add("int cuGrid[3]");
        kernelPara.add("int cuBlock[3]");

        athreadLocalVar.add("\n  //ATHREAD local variable definition");
        athreadLocalVar.add("\n  int swGrid[3];");
        athreadLocalVar.add("\n  int swBlock[3];");
        athreadLocalVar.add("\n  int stride;");
        athreadLocalVar.add("\n  int blockId_x,blockId_y,blockId_z;");
        athreadLocalVar.add("\n  int threadId_x,threadId_y,threadId_z;\n\n");

        athreadBasicOp.add("\n  //ATHREAD basic operation");
        athreadBasicOp.add("\n  blockIndex = CRTS_tid;");
        athreadBasicOp.add("\n  Para_s = (Para*)ldm_malloc(sizeof(Para));");
        athreadBasicOp.add("\n  CRTS_dma_iget(Para_s, Paras, sizeof(Para), &dma_rply);");
        athreadBasicOp.add("\n  D_COUNT++;");
        athreadBasicOp.add("\n  CRTS_dma_wait_value(&dma_rply, D_COUNT);\n\n");

        atomsendDataFunc.add("\n/*! \\brief write conflict interface\n" +
                " * send data and position from computing core to receive core\n" +
                " *\n" +
                " * \\param[in]     targetCore       target receive core id\n" +
                " * \\param[in]     dataSize         sending data size\n" +
                " * \\param[in]     localData        local sending data pointer\n" +
                " * \\param[in]     remoteData       receiving core data pointer to get the data from computing core\n" +
                " * \\param[in]     remote_rply      reply flag to indicate if it is received from receiving core\n" +
                " */");

        atomRcvCoreFunc.add("\n/*! \\brief write conflict interface\n" +
                " *  receive core actions, get spliting data from host by DMA, set up while loop to\n" +
                " *  receive data and do accumulation from computing core.\n" +
                " * \\param[in]     hostData         this data pointer is strided with different id\n" +
                " * \\param[in]     dma_Size          dma size for data transfering\n" +
                " * \\param[in]     Stride           data stride for each receving core\n" +
                " */");


        blockAffineTrans.append("  //cuda grid and block affine translation\n" +
                "  int blockSum   = Para_s->cuGrid[0] * Para_s->cuGrid[1] * Para_s->cuGrid[2];\n" +
                "  int threadSum  = Para_s->cuBlock[0] * Para_s->cuBlock[1] * Para_s->cuBlock[2];\n" +
                "  int quotient   = blockSum / 64; \n" +
                "  int remainder  = blockSum % 64; \n" +
                "  int blockStart = quotient * blockIndex;\n" +
                "  if(remainder > 0)\n" +
                "    blockStart += (remainder > blockIndex ? blockIndex : remainder);\n" +
                "  int blockEnd = blockStart + quotient;\n" +
                "  if(remainder > 0)\n" +
                "    blockEnd   += (remainder > blockIndex ? 1 : 0); \n\n");

        affineTransOp.append("\n  for(int block = blockStart; block < blockEnd; block++){\n" +
                "    blockId_z = block / (Para_s->cuGrid[0] * Para_s->cuGrid[1]);\n" +
                "    blockId_y = (block - blockId_z * (Para_s->cuGrid[0] * Para_s->cuGrid[1])) / Para_s->cuGrid[0];\n" +
                "    blockId_x = block % Para_s->cuGrid[0];\n\n");

        threadTffineTrans.append(
                "    //ATHREAD serial operation replace GPU parallel mutli-thread in single thread-block\n" +
                "    for(int threadId = 0; threadId < threadSum; threadId++) {\n" +
                "      threadId_x = threadId % Para_s->cuBlock[0];\n" +
                "      threadId_z = threadId / (Para_s->cuBlock[0] * Para_s->cuBlock[1]);\n" +
                "      threadId_y = (threadId - threadId_z * (Para_s->cuBlock[0] * Para_s->cuBlock[1])) / Para_s->cuBlock[0];\n\n" );

        writeConflictInit.append("\n/*! \\brief write conflict interface\n" +
                " * write conflict init: to get data stride and dma size\n" +
                " * totalSize < 128K RCV_CORE=1, 128K<totalSize<256K, RCV_CORE=2,\n" +
                " * 256K<totalSize<512K, RCV_CORE=4, totalSize>512K, RCV_CORE=8,\n" +
                " * \\param[in]     totalSize        target total data size for sparse write\n" +
                " * \\param[out]    dma_size         get data size for each receving core prepared for dma\n" +
                " * \\param[out]    data_stride      data stride for each receiving core\n" +
                " */\n" +
                "void write_conflict_init(int totalSize, int *dma_size, int *data_stride)\n" +
                "{   \n" +
                "    /* 2. write conflict data split */\n" +
                "    // since the forceSize is atomsize * 3, we first split forceSize/3 to get the atomSize\n" +
                "    \n" +
                "    *data_stride = (totalSize + RCV_CORE - 1) / RCV_CORE;\n" +
                "    if(blockIndex == 63)\n" +
                "        *dma_size = totalSize - *data_stride * (RCV_CORE - 1);\n" +
                "    else\n" +
                "        *dma_size = *data_stride;\n" +
                "}\n");

        finshDataSending.append("\n/*! \\brief write conflict interface\n" +
                " * \\param[in]     lastid1       last target id communication to check in transaction1\n" +
                " * notify receiving core to stop receiving data loop\n" +
                " */\n" +
                "void finish_data_sending(int lastid, volatile int *remote_rply)\n" +
                "{\n" +
                "    while(1)\n" +
                "    {\n" +
                "        int targetid = CP_CORE + lastid;\n" +
                "        cpt_rply = (volatile int*)remote_ldm_addr(targetid, *remote_rply);\n" +
                "        if(*cpt_rply == 0)\n" +
                "            break;\n" +
                "    }\n" +
                "    compute_end = 1;\n" +
                "    for(int i = 0; i < RCV_CORE; i++)\n" +
                "    {\n" +
                "        int targetid = i + CP_CORE;\n" +
                "        CRTS_rma_iput(&compute_end,&l_rply,sizeof(int),targetid,&r_end[blockIndex],&r_rply);\n" +
                "    }\n" +
                "    while (l_rply != RCV_CORE);\n" +
                "    l_rply = 0;\n" +
                "}\n");

        primConvert.put("gridDim.x","Para_s->cuGrid[0]");
        primConvert.put("gridDim.y","Para_s->cuGrid[1]");
        primConvert.put("gridDim.z","Para_s->cuGrid[2]");
        primConvert.put("blockDim.x","Para_s->cuBlock[0]");
        primConvert.put("blockDim.y","Para_s->cuBlock[1]");
        primConvert.put("blockDim.z","Para_s->cuBlock[2]");
        primConvert.put("blockIdx.x","blockId_x");
        primConvert.put("blockIdx.y","blockId_y");
        primConvert.put("blockIdx.z","blockId_z");
        primConvert.put("threadIdx.x","threadId_x");
        primConvert.put("threadIdx.y","threadId_y");
        primConvert.put("threadIdx.z","threadId_z");

    }

    //current class name
    public String curclassname=null;

    //current baseclassname
    public String Baseclasshead=null;
    //save all the base class list
    public List<String> baseclassname = new ArrayList<>();
    //save all the abstract class name list
    public List<String> abstractclassname = new ArrayList<>();


    //inherit class need to know the base class functions and variable
    // it is map<inherit,base> mode
    public Map<String, String> inheritclass = new IdentityHashMap();
    public Map<String, String> absinheritclass = new IdentityHashMap();

    //save include and define list
    public List<String> includedefinelist = new ArrayList<>();
    //athread DMA  definition
    public List<String> athreadDMA = new ArrayList<>();
    public List<String> globalDef = new ArrayList<>();

    public List<String> dynamicDef = new ArrayList<>();
    public List<String> atomicDef = new ArrayList<>();
    public List<String> kernelPara = new ArrayList<>();

    public List<String> athreadLocalVar = new ArrayList<>();

    public List<String> athreadBasicOp = new ArrayList<>();

    public StringBuilder affineTransOp = new StringBuilder();

    public StringBuilder blockAffineTrans = new StringBuilder();
    public StringBuilder threadTffineTrans = new StringBuilder();

    public StringBuilder writeConflictInit = new StringBuilder();

    public StringBuilder finshDataSending = new StringBuilder();
    public List<String> atomsendDataFunc = new ArrayList<>();

    public List<String> atomRcvCoreFunc = new ArrayList<>();

    public globalScope globalScopeDef = new Utility.globalScope();

    private static Map<String, String> primConvert = new IdentityHashMap();

    public List<pointerDataAttr> pointerDataAttrList = new ArrayList<>();


    private static Logger logger = LogManager.getLogger("Utility");

    public static class globalScope{
        public enum glbScope {NONE,KERNERENTRY, DOMAINDEF, DEVFUNC, PARMETER}
        private glbScope currentscope;
        // indicate if we need transfer data by dma with stride, accquired from pointerDataAttr.
        private boolean dmaStride;
        // indicate max data direction dimention, acquired from the dataPatten
        private dataPattern.type maxDim;

        //indicate cuda thread index pointer
        //for example: i=blockId_x *Para_s->cuBlock[0]+threadId_x, xInd = i
        private String xInd;
        private String yInd;
        private String zInd;

        public void setScope(glbScope type){
            currentscope = type;
            logger.info("set currentscope\t"+currentscope.toString());
        }
        public glbScope getScope() {
            return currentscope;
        }
        public globalScope(){
            currentscope = glbScope.NONE;
            dmaStride = false;
            maxDim = dataPattern.type.NONE;
        }

        public dataPattern.type getMaxDim() {
            return maxDim;
        }

        public void setMaxDim(dataPattern.type maxDim) {
            this.maxDim = maxDim;
        }

        public boolean getDmaStride() { return dmaStride; }

        public void setDmaStride(boolean dmaStride) {
            this.dmaStride = dmaStride;
        }

        public String getxInd() {
            return xInd;
        }

        public void setxInd(String xInd) {
            this.xInd = xInd;
        }

        public String getyInd() {
            return yInd;
        }

        public void setyInd(String yInd) {
            this.yInd = yInd;
        }

        public String getzInd() {
            return zInd;
        }

        public void setzInd(String zInd) {
            this.zInd = zInd;
        }
    }

    public static class directive{
        public enum directiveType {NONE,PARAVAR,ATOMVAR,DATAREDIRECT};
        private directiveType dirType;
        private boolean atomOpr;
        public directive(){
            dirType = directiveType.NONE;
            atomOpr = false;
        }

        public void setDirType(directiveType dirType) {
            this.dirType = dirType;
        }

        public directiveType getDirType() {
            return dirType;
        }

        public boolean isAtomOpr() {
            return atomOpr;
        }

        public void setAtomOpr(boolean atomOpr) {
            this.atomOpr = atomOpr;
        }
    }

    public static String sunwayTranslation(String cudaPrim){
        String label = null;
        for (String key : primConvert.keySet()) {
            if (key.equals(cudaPrim))
                label = primConvert.get(key);
           // logger.info("key: "+key+" cudaPrim: "+cudaPrim + " label: " + label);
        }
        return label;
    }


    public static class pointerDataAttr{
        private String type;
        private String variable;
        private String dataSize;
        private String attribute;

        private boolean dmaWithStride;
        private boolean ldmByDma;
        private boolean glbSharedMem;

        private boolean definedInCPE;

        //for atomic operation
        private int rcvCoreNum;
        private int rmaDataNum;
        private boolean copyin;

        public enum label {DONE,TYPE, VAR, SIZE, ATTR,STRIDE,LDM,SHARED};

        public enum atomPara {DONE,TYPE, VAR, SIZE,RCVCORE, RMADATA, COPYIN};

        private label curState = label.TYPE;
        private atomPara atomState = atomPara.TYPE;

        public pointerDataAttr(){
            this.type = null;
            this.variable = null;
            this.dataSize = null;
            this.attribute = null;
            this.dmaWithStride = false;
            this.ldmByDma = false;
            this.glbSharedMem = false;
            this.curState = label.TYPE;
            this.atomState = atomPara.TYPE;
            this.definedInCPE = false;
            this.copyin = false;
        }
        public pointerDataAttr(pointerDataAttr obj){
            this.type = obj.type;
            this.variable = obj.variable;
            this.dataSize = obj.dataSize;
            this.attribute = obj.attribute;
            this.dmaWithStride = obj.dmaWithStride;
            this.ldmByDma = obj.ldmByDma;
            this.glbSharedMem = obj.glbSharedMem;
            this.definedInCPE = obj.definedInCPE;
            this.curState = label.TYPE;
            this.atomState = atomPara.TYPE;
        }
        public String getType(){
            return type;
        }
        public void setType(String type){
            this.type = type;
        }
        public String getVariable(){
            return variable;
        }
        public void setVariable(String variable) {
            this.variable = variable;
        }
        public String getDataSize(){
            return dataSize;
        }
        public void setDataSize(String dataSize){
            this.dataSize = dataSize;
        }
        public String getAttribute(){
            return attribute;
        }
        public void setAttribute(String attribute){
            this.attribute = attribute;
        }

        public boolean getdmaWithStride(){
            return dmaWithStride;
        }
        public void setdmaWithStride(boolean dmaWithStride){
            this.dmaWithStride = dmaWithStride;
        }
        public boolean getldmByDma(){
            return ldmByDma ;
        }
        public void setldmByDma(boolean ldmByDma){
            this.ldmByDma = ldmByDma;
        }
        public boolean getglbSharedMem(){
            return glbSharedMem;
        }
        public void setGlbSharedMem(boolean glbSharedMem){
            this.glbSharedMem = glbSharedMem;
        }

        public void setCurState(label mystate){
            this.curState = mystate;
        }

        public label getCurState(){
            return curState;
        }

        public void setAtomState(atomPara atomState) {
            this.atomState = atomState;
        }

        public atomPara getAtomState() {
            return atomState;
        }

        public boolean isCopyin() {
            return copyin;
        }

        public void setCopyin(boolean copyin) {
            this.copyin = copyin;
        }

        public int getRmaDataNum() {
            return rmaDataNum;
        }

        public void setRmaDataNum(int rmaDataNum) {
            this.rmaDataNum = rmaDataNum;
        }

        public void setRcvCoreNum(int rcvCoreNum) {
            this.rcvCoreNum = rcvCoreNum;
        }

        public int getRcvCoreNum() {
            return rcvCoreNum;
        }

        public void setDefinedInCPE(boolean definedInCPE){
            this.definedInCPE = definedInCPE;
        }

        public boolean getDefinedInCPE(){
            return definedInCPE;
        }
    }

    public static class dataPattern{

        private StringBuilder stride;
        private StringBuilder locality;
        private String pattern1;
        private String pattern2;

        private String pattern3;

        private String variable;

        private String varType;
        private String dataUsage;

        private String zInd;
        private String zDim;
        private String yInd;
        private String yDim;

        private String xInd;
        public enum label {DONE,PATTERN1,PATTERN2,PATTERN3,VAR,VARTYPE,USAGE}

        public enum type {ONEDIM,TWODIM,THREEDIM,NONE}

        private label patternState = label.PATTERN1;

        private type pattenType = type.ONEDIM;

        public label getPatternState() {
            return patternState;
        }

        public void setPatternState(label patternState) {
            this.patternState = patternState;
        }

        public dataPattern(){
            stride = new StringBuilder();
            locality = new StringBuilder();
            patternState = label.PATTERN1;
            pattenType = type.NONE;
        }

        public dataPattern(dataPattern obj){
            this.variable = obj.variable;
            this.pattenType = obj.pattenType;
            this.pattern1 = obj.pattern1;
            this.pattern2 = obj.pattern2;
            this.pattern3 = obj.pattern3;
            this.dataUsage = obj.dataUsage;
            this.stride = obj.stride;
            this.locality = obj.locality;
            this.varType = obj.varType;
            this.zInd = obj.zInd;
            this.zDim = obj.zDim;
            this.yInd = obj.yInd;
            this.yDim = obj.yDim;
            this.xInd = obj.xInd;
        }

        public type getPattenType() {
            return pattenType;
        }

        public void setPattenType(type pattenType) {
            this.pattenType = pattenType;
        }

        public String getDataUsage() {
            return dataUsage;
        }

        public void setDataUsage(String dataUsage) {
            this.dataUsage = dataUsage;
        }

        public String getVariable() {return variable; }

        public void setVariable(String variable) { this.variable = variable; }

        public String getVarType() {
            return varType;
        }

        public void setVarType(String varType) {
            this.varType = varType;
        }

        public String getPattern1() {
            return pattern1;
        }

        public void setPattern1(String pattern1) {
            this.pattern1 = pattern1;
        }

        public String getPattern2() {
            return pattern2;
        }

        public void setPattern2(String pattern2) {
            this.pattern2 = pattern2;
        }

        public String getPattern3() {
            return pattern3;
        }
        public void setPattern3(String pattern3) {
            this.pattern3 = pattern3;
        }

        public String getzInd() {
            return zInd;
        }

        public void setzInd(String zInd) {
            this.zInd = zInd;
        }

        public String getyInd() {
            return yInd;
        }

        public String getxInd() {
            return xInd;
        }

        public String getyDim() {
            return yDim;
        }

        public String getzDim() {
            return zDim;
        }

        public void usageTranslate(){
            String d1,d2,d3;
            String e1,e2,e3;


            if(pattenType.equals(type.THREEDIM)){
                // k*imt*jmt + j*jmt + i
                this.zInd = pattern3.substring(0, pattern3.indexOf("=")).trim();
                this.yInd = pattern2.substring(0,pattern2.indexOf("=")).trim();
                this.xInd = pattern1.substring(0,pattern1.indexOf("=")).trim();
                //d1: k*imt*jmt, d2: j*jmt d3:i
                d1 = dataUsage.substring(0,dataUsage.indexOf("+")).trim();
                d2 = dataUsage.substring(dataUsage.indexOf("+")+1, dataUsage.lastIndexOf("+")).trim();
                d3 = dataUsage.substring(dataUsage.lastIndexOf("+")+1).trim();

                e1 = d1.substring(0,d1.indexOf("*")).trim();
                e2 = d1.substring(d1.indexOf("*")+1, d1.lastIndexOf("*")).trim();
                e3 = d1.substring(d1.lastIndexOf("*")+1).trim();
                if(e1.equals(this.zInd))
                    this.zDim = e2 + "*" + e3;
                else if(e2.equals(this.zInd))
                    this.zDim = e1 + "*" + e3;

                else if(e3.equals(this.zInd))
                    this.zDim = e1 + "*" + e2;
                logger.info("usageTranslate: d1: " + d1 + " d2: "+ d2 + " e1: " + e1 + " e2: " + e2 + " e3: " + e3);

                e1 = d2.substring(0,d2.indexOf("*")).trim();
                e2 = d2.substring(d2.lastIndexOf("*")+1).trim();
                if(e1.equals(this.yInd))
                    this.yDim = e2;

                else if(e2.equals(this.yInd))
                    this.yDim = e1;
            }
            else if(pattenType.equals(type.TWODIM)){
                // j*jmt + i
                this.yInd = pattern2.substring(0,pattern2.indexOf("=")).trim();
                this.xInd = pattern1.substring(0,pattern1.indexOf("=")).trim();
                d1 = dataUsage.substring(0,dataUsage.indexOf("+")).trim();
                d2 = dataUsage.substring(dataUsage.lastIndexOf("+")+1).trim();
                e1 = d2.substring(0,d1.indexOf("*")).trim();
                e2 = d2.substring(d1.lastIndexOf("*")+1).trim();
                if(this.yInd.equals(e1))
                    this.yDim = e2;

                else if(yInd.equals(e2))
                    this.yDim = e1;
            }
            logger.info("usageTranslate: zInd: " + this.zInd + " zDim: "+ this.zDim + " yInd: " + this.yInd + " yDim: " + this.yDim);
        }
    }


}